package com.github.sbb.sherry.primitives.rules;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.github.sbb.sherry.common.primitives.StringUtil;
import com.github.sbb.sherry.primitives.api.BaseParamConverter;
import com.github.sbb.sherry.primitives.api.Converter;
import com.github.sbb.sherry.primitives.api.Fact;
import com.github.sbb.sherry.primitives.api.Facts;
import com.github.sbb.sherry.primitives.api.FactsSupplier;
import com.github.sbb.sherry.primitives.api.Fetcher;
import com.github.sbb.sherry.primitives.api.FetcherContainer;
import com.github.sbb.sherry.primitives.config.calcparam.ExpressionSymbol;
import com.github.sbb.sherry.primitives.config.calcparam.RuleParamConfig;
import com.github.sbb.sherry.primitives.config.calcparam.RuleParamConfigContainer;
import com.github.sbb.sherry.primitives.config.calcparam.RuleParamTypeEnum;
import com.github.sbb.sherry.primitives.config.fetcher.FetcherMethodEnum;
import com.github.sbb.sherry.primitives.core.BaseConverterPool;
import com.github.sbb.sherry.primitives.core.ConverterPool;
import com.github.sbb.sherry.primitives.core.MapFact;
import com.github.sbb.sherry.primitives.exception.FetcherException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.jexl3.JexlContext;
import org.apache.commons.jexl3.JexlExpression;
import org.apache.commons.jexl3.MapContext;

public class CalcParamFactsSupplier implements FactsSupplier {

  private List<RuleParamConfig> ruleParamConfigs;
  private FetcherContainer fetcherContainer;
  private RuleParamConfigContainer ruleParamConfigContainer;

  Set<Fetcher> needFetchFetchers = new HashSet<>();
  List<RuleParamConfig> orderedRuleParamConfigs;


  /**
   * 计算参数和 Fetcher 的关系
   */
  private Map<String, Set<Fetcher>> rulesParamFetcherMap = new HashMap<>();

  public CalcParamFactsSupplier(List<RuleParamConfig> ruleParamConfigs,
      FetcherContainer fetcherContainer,
      RuleParamConfigContainer ruleParamConfigContainer) {

    this.ruleParamConfigs = ruleParamConfigs;
    this.fetcherContainer = fetcherContainer;
    this.ruleParamConfigContainer = ruleParamConfigContainer;

    this.orderedRuleParamConfigs = new ArrayList<>(ruleParamConfigs.size());

    for (RuleParamConfig ruleParamConfig : ruleParamConfigs) {
      needFetchFetchers.addAll(obtainSpoutFromRulesParam(ruleParamConfig, ruleParamConfigs));
    }
  }

  private Set<Fetcher> obtainSpoutFromRulesParam(final RuleParamConfig ruleParamConfig, List<RuleParamConfig> orderedRuleParamConfigs) {
    Set<Fetcher> fetchers = new HashSet<>();
    // 递归获取所有的 mapper
    recursionDealRulesMapper(ruleParamConfig, fetchers, orderedRuleParamConfigs);
    rulesParamFetcherMap.put(ruleParamConfig.getName(), fetchers);
    return fetchers;
  }

  private void recursionDealRulesMapper(final RuleParamConfig ruleParamConfig, Set<Fetcher> fetchers,
      List<RuleParamConfig> orderedRuleParamConfigs) {

    Set<Fetcher> fetcherSet = rulesParamFetcherMap.get(ruleParamConfig.getName());
    if (fetcherSet != null) {
      fetchers.addAll(fetcherSet);
      return;
    }

    if (ruleParamConfig.getType() == RuleParamTypeEnum.FETCHER.getValue()) {
      // fetcher 参数, 是基础参数，取到 fetcher 后可以直接放入 rulesParamMapperMap 中
      Fetcher fetcher = fetcherContainer.get(ruleParamConfig.getFetcher());
      rulesParamFetcherMap.put(ruleParamConfig.getName(), new HashSet<Fetcher>() {{
        add(fetcher);
      }});
      fetchers.add(fetcher);
      orderedRuleParamConfigs.add(ruleParamConfig);
    } else if (ruleParamConfig.getType() == RuleParamTypeEnum.DERIVE.getValue()) {

      // 衍生参数
      // 衍生参数的获取 fetcher 本身比较麻烦，因为衍生参数的的计算参数本身也可能是衍生参数，递归一下
      List<ExpressionSymbol> expressionSymbols = JSON.parseObject(ruleParamConfig.getExpressionSymbols(),
          new TypeReference<List<ExpressionSymbol>>() {
          });

      // 每次算衍生参数都要重新创建 mapperConfigs，保证 mapperConfigs 里面只计算出当前的 RulesParams
      Set<Fetcher> fetcherSet2 = new HashSet<>();
      expressionSymbols.stream().filter(it -> it.getType() == 1).forEach(symbol -> {
        recursionDealRulesMapper(ruleParamConfigContainer.get(symbol.getSymbol()), fetcherSet2, orderedRuleParamConfigs);
      });

      orderedRuleParamConfigs.add(ruleParamConfig);
      rulesParamFetcherMap.put(ruleParamConfig.getName(), fetcherSet2);
      fetchers.addAll(fetcherSet2);
    }
  }

  /**
   * 从目标 spout 中并行获取信息
   *
   * @param job 参数信息
   */
  private Map<String, Object> fetchFetchers(Map<String, Object> params) throws FetcherException {
    Map<String, Object> result = new HashMap<>();
    for (Fetcher fetcher : needFetchFetchers) {
      byte defaultFetchMethod = fetcher.getDefaultFetchMethod();
      if (defaultFetchMethod == FetcherMethodEnum.EMIT.getValue()) {
        Fact fact = fetcher.emit(params);
        result.put(fetcher.name(), fact);
      } else {
        Facts facts = fetcher.batchEmit(params);
        result.put(fetcher.name(), facts);
      }
    }
    return result;
  }

  private Map<String, Object> convertAndDeriveParams(Map<String, Object> fetchResultMap) {
    Map<String, Object> finalParamResult = new HashMap<>();
    for (RuleParamConfig orderedRuleConfig : orderedRuleParamConfigs) {
      if (orderedRuleConfig.getType() == RuleParamTypeEnum.FETCHER.getValue()) {
        if (StringUtil.isBlank(orderedRuleConfig.getConverter())) {
          Fact fact = (Fact) fetchResultMap.get(orderedRuleConfig.getFetcher());
          // 无需转换器
          if (fact == null || fact.get(orderedRuleConfig.getParamAlias()) == null) {
            BaseParamConverter baseParamConverter = BaseConverterPool
                .getByParamType(orderedRuleConfig.getParameterType());
            Object convertResult = baseParamConverter.convert(orderedRuleConfig.getDefaultValue());
            finalParamResult.put(orderedRuleConfig.getName(), convertResult);
          } else {
            finalParamResult.put(orderedRuleConfig.getName(), fact.get(orderedRuleConfig.getParamAlias()));
          }
        } else {
          // 需要转换器
          Converter converter = ConverterPool
              .getConverter(orderedRuleConfig.getConverter());
          Object object = fetchResultMap.get(orderedRuleConfig.getFetcher());

          Object result;

          // 算出默认值
          if (object instanceof Fact) {
            result = converter.convert(orderedRuleConfig.getParamAlias(), (Fact) object);
          } else {
            result = converter.convert(orderedRuleConfig.getParamAlias(), (Facts) object);
          }

          if (result == null) {
            BaseParamConverter baseParamConverter = BaseConverterPool.getByParamType(orderedRuleConfig.getParameterType());
            result = baseParamConverter.convert(orderedRuleConfig.getDefaultValue());
          }

          finalParamResult.put(orderedRuleConfig.getName(), result);
        }
      } else {
        // 衍生参数，直接计算
        String expression = orderedRuleConfig.getExpression();
        // 此处可以用缓存
        JexlExpression jexlExpression = JexlUtil.createExpression(expression);
        JexlContext context = new MapContext(finalParamResult);
        Object evaluateResult = jexlExpression.evaluate(context);
        finalParamResult.put(orderedRuleConfig.getName(), evaluateResult);
      }
    }

    return finalParamResult;
  }

  @Override
  public Facts get(Map<String, Object> params) throws FetcherException {
    Map<String, Object> fetchResultMap = fetchFetchers(params);
    Map<String, Object> finalParamResult = convertAndDeriveParams(fetchResultMap);
    Facts facts = new Facts();
    MapFact mapFact = new MapFact(finalParamResult);
    facts.add(mapFact);
    return facts;
  }
}
